module spi (
    input clk,
    input resetn,

    input mem_valid,
    input [31:0] mem_addr,
    output reg [31:0] mem_rdata,
    input [31:0] mem_wdata,
    input [3:0] mem_wstrb,
    output reg mem_ready,

    output spi_clk,
    output spi_mosi,
    input spi_miso
);

wire spi_clk_inter;
reg spi_clk_control;
reg spi_clk_control_pos;
reg [3:0] spi_div;
reg [7:0] spi_freq;
always @(posedge clk ) begin
    if(!resetn)begin
        spi_freq <= 8'h0;
    end
    else begin
        spi_freq <= spi_freq + 1'b1;
    end
end
assign spi_clk_inter = (spi_div == 4'd0) ? clk :
                        (spi_div == 4'd1) ? spi_freq[0] :
                        (spi_div == 4'd2) ? spi_freq[1] :
                        (spi_div == 4'd3) ? spi_freq[2] :
                        (spi_div == 4'd4) ? spi_freq[3] :
                        (spi_div == 4'd5) ? spi_freq[4] :
                        (spi_div == 4'd6) ? spi_freq[5] :
                        (spi_div == 4'd7) ? spi_freq[6] :
                        (spi_div == 4'd8) ? spi_freq[7] :
                        spi_freq[7];
assign spi_clk = spi_clk_control & spi_clk_inter;
always @(negedge clk ) begin
    if((spi_clk_inter == 1'b0) ) begin
        spi_clk_control <= spi_clk_control_pos & resetn;
    end
end
reg [7:0] spi_shift_o;
assign spi_mosi = spi_shift_o[7];

localparam spi_idle = 2'b0;
localparam spi_rw   = 2'b1;
reg [7:0] spi_dor;
reg [7:0] spi_dir;

reg spi_state;
reg [2:0] spi_cnt;
always @(posedge spi_clk_inter or negedge resetn) begin
    if(!resetn)begin
        spi_state <= spi_idle;
        spi_clk_control_pos <= 1'b0;
        spi_cnt <= 3'd0;
        spi_dir <= 8'd0;
        spi_shift_o <= 8'd0;
    end
    else begin
        case(spi_state)
        spi_idle:begin
            spi_state <= spi_idle;
            spi_shift_o <= 8'd0;
            spi_clk_control_pos <= 1'b0;
            spi_cnt <= 3'd0;
            if(spi_start)begin 
                spi_clk_control_pos <= 1'b1;
                spi_shift_o <= spi_dor;
                spi_state <= spi_rw;
            end
        end
        spi_rw:begin
            spi_state <= spi_rw;
            spi_dir <= {spi_dir[6:0],spi_miso};
            spi_shift_o <= {spi_shift_o[6:0],1'b1};
            spi_cnt <= spi_cnt + 1'b1;
            if(spi_cnt == 3'd7) begin
                spi_state <= spi_idle;
                spi_cnt <= 3'd0;
                spi_clk_control_pos <= 1'b0;
            end
        end
        default:begin
            spi_state <= spi_idle;
        end
        endcase
    end
end

wire mem_valid_neg;
reg mem_valid_r0;
always@(posedge clk) mem_valid_r0 <= mem_valid;
assign mem_valid_neg = mem_valid_r0 & (~mem_valid);

reg spi_start;
always @(posedge clk or negedge resetn) begin
    if(!resetn)begin
        mem_ready <= 1'b0;
        spi_dor <= 8'd0;
        spi_start <= 1'b0;
        mem_rdata <= 32'h0;
        spi_div <= 4'h0;
    end
    else begin
        if(spi_state == spi_rw) begin
            spi_start <= 1'b0;
        end
        if((mem_valid == 1'b1)&&(mem_ready == 1'b0)) begin
            
            case(mem_addr)
            32'h1000_0004:begin
                if(mem_wstrb != 0) begin
                    spi_start <= 1'b1;
                    spi_dor <= mem_wdata[7:0];
                end
                else begin
                    mem_rdata <= {16'h0,{(spi_state != spi_rw)|spi_clk},7'd0,spi_dir};
                end
                mem_ready <= 1'b1;
            end
            32'h1000_0008:begin
                if(mem_wstrb != 0) begin
                    spi_div <= mem_wdata[3:0];
                end
                else begin
                    mem_rdata <= {28'h0,spi_div[3:0]};
                end
                mem_ready <= 1'b1;
            end
            default: mem_ready <= 1'b1;
            endcase
        end
        else if(mem_valid_neg) begin
            mem_ready <= 1'b0;
        end
        else begin
            mem_ready <= mem_ready;
        end
    end
    
end    
endmodule